package controllers

import (
	"strings"

	"gitee.com/yizhongi/wfw/models"
	"gitee.com/yizhongi/wfw/util"
	"gitee.com/yizhongi/wfw/valid"
	"github.com/beego/beego/v2/adapter/logs"
	"github.com/beego/beego/v2/adapter/orm"
	"github.com/beego/beego/v2/server/web"
	"golang.org/x/crypto/bcrypt"
)

// UserRegisterController 注册控制器
type UserRegisterController struct {
	web.Controller
}

//Get 响应get方法
// @router /register [get]
func (c *UserRegisterController) Get() {
	c.TplName = "user/signup.html"
}

//Post 响应Post方法
// @router /register [post]
func (c *UserRegisterController) Post() {
	l := logs.GetBeeLogger()
	l.SetPrefix("用户注册API：")
	// 内部用户注册
	// 获取Post表单数据
	//registerToken := strings.TrimSpace(c.GetString("token"))
	// 验证Token - 这里是在App.conf里面设置的registertoken
	// if registerToken != web.AppConfig.String("registertoken") {
	// 	l.Debug("注册测试Token验证失败,用户输入:%s,系统环境设置:%s", registerToken, web.AppConfig.String("registertoken"))
	// 	c.Ctx.ResponseWriter.WriteHeader(403)
	// 	// util.CreateMsg - 这个是我自己写的方法，请参考我博文下面的源代码
	// 	//c.Data["json"] = util.CreateMsg(403, "token验证失败")
	// 	c.ServeJSON()
	// 	return
	// }
	// 获取表单数据  - 去除两边的空格
	username := strings.TrimSpace(c.GetString("username"))
	password := strings.TrimSpace(c.GetString("password"))
	// 验证表单数据 - 这里使用官方表单数据验证包（结合自定义验证）
	// github.com/astaxie/beego/validation
	// 可以下面附加的验证工具，或者直接把这一段删掉
	u := &valid.ValidateUser{
		Username: username,
		Password: password,
	}
	err := u.ValidUser()
	if err != nil {
		l.Debug("用户注册失败，原因:%s", err.Error())
		c.Ctx.ResponseWriter.WriteHeader(403)
		//c.Data["json"] = util.CreateMsg(403, err.Error())
		c.ServeJSON()
		return
	}
	// 生成加密密码 - 这里使用了Golang官方加密包
	// golang.org/x/crypto/bcrypt
	// 注意 - 每次生成的密码都不一样，但是没关系
	// 登陆是验证原密码再加密后是否能和存储的加密是否一致
	// bcrypt.DefaultCost = 10
	encodePW, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
	if err != nil {
		l.Debug("密码加密失败", err)
		c.Ctx.ResponseWriter.WriteHeader(500)
		//c.Data["json"] = util.CreateMsg(500, "密码生成失败")
		c.ServeJSON()
		return
	}
	// 创建User对象
	user := models.User{Username: username, Password: string(encodePW)}
	// 创建ORM对象
	o := orm.NewOrm()
	// ReadOrCreate - 先读取再创建
	status, id, err := o.ReadOrCreate(&user, "username")
	// 数据库操作失败
	if err != nil {
		l.Error("发生错误:%s", err)
		c.Ctx.ResponseWriter.WriteHeader(500)
		//c.Data["json"] = util.CreateMsg(500, "数据库连接失败，用户注册失败。")
		c.ServeJSON()
		return
	}
	// 创建失败 - 用户名已经存在
	if !status {
		l.Debug("用户名已经存在，id为%d", id)
		c.Ctx.ResponseWriter.WriteHeader(403)
		//c.Data["json"] = util.CreateMsg(403, "用户注册失败：用户已存在")
		c.ServeJSON()
		return
	}
	// 如果需要注册后直接保持登陆状态
	// 设置服务器Session存储
	// c.SetSession("userid", user.Id)
	// c.SetSession("password", password) // 这里存储的是原始密码
	// 创建用户成功 - 返回用户名
	//c.Data["json"] = util.CreateMsg(200, fmt.Sprintf("用户%s注册成功", username))
	c.ServeJSON()
}

//UserStatusController 用户状态控制器
type UserStatusController struct {
	web.Controller
	isLogin bool        // 登陆状态标记
	User    models.User // 登陆的用户
}

//Prepare 初始化用户的登录状态
func (c *UserStatusController) Prepare() {
	// 设置默认值
	c.isLogin = false
	c.Data["isLogin"] = false
	c.User = models.User{}
	// 获取Session信息 - 注意这里返回的是interface类型
	useridInterface := c.GetSession("uid")
	userid, ok := useridInterface.(int)
	// 优化性能，如果Id不存在就不需要再获取Password
	if !ok {
		return
	}
	// 接下来是验证Password是否被更新 - 防止数据库密码更新后仍保持Session
	passwordInterface := c.GetSession("pwd")
	password, _ := passwordInterface.(string)
	user := models.User{ID: userid}
	// ORM对象
	o := orm.NewOrm()
	// 这里如果在其他地方没有用到用户其他字段的话
	// 建议改成One方法仅返回密码来判断
	// 即 o.QueryTable("user").Filter("id", &user.id).One(&user,"password")
	err := o.Read(&user)
	// 如果无法读取到用户就结束
	if err != nil {
		return
	}
	// 判断密码是否修改过
	if user.Password != util.Md5Pwd(password) {
		return
	}
	// 最后才设置登陆状态
	c.isLogin = true
	c.Data["isLogin"] = true
	c.User = user
}

//UserLoginController 用户登录控制器
type UserLoginController struct {
	UserStatusController
}

var loginTpl = "user/login.html"

//Get 登录页面 get
// @router /login [get]
func (c *UserLoginController) Get() {
	c.TplName = loginTpl
}

//Post 用户登录
// @router /login [post]
func (c *UserLoginController) Post() {
	flash := web.NewFlash()
	// 创建logs（记录器）
	l := logs.BeeLogger{}
	l.SetPrefix("用户登陆API：")
	// 判断是否已经登陆 - 已登陆就终结登陆流程
	if c.isLogin == true {
		l.Debug("用户已登陆")
		c.Ctx.ResponseWriter.WriteHeader(202)
		// util.CreateMsg - 这个是我自己写的方法，请参考我博文下面的源代码
		//c.Data["json"] = util.CreateMsg(202, "用户已登陆")
		//flash.Error("用户已登陆")
		//flash.Store(&c.Controller)
		c.TplName = loginTpl
		return
	}
	// 用户登陆
	username := strings.TrimSpace(c.GetString("username"))
	password := strings.TrimSpace(c.GetString("password"))
	// 验证表单数据 - 这里使用官方表单数据验证包（结合自定义验证）
	// github.com/astaxie/beego/validation
	// 可以下面附加的验证工具，或者直接把这一段删掉
	u := &valid.ValidateUser{
		Username: username,
		Password: password,
	}
	err := u.ValidUser()
	if err != nil {
		l.Debug("用户登录失败，原因:%s", err.Error())
		c.Ctx.ResponseWriter.WriteHeader(403)
		//c.Data["json"] = util.CreateMsg(403, err.Error())
		//flash.Error("登陆失败")
		//flash.Store(&c.Controller)
		c.TplName = loginTpl
		return
	}
	// 创建User对象 - 不设置密码
	user := models.User{Username: username}
	// 创建ORM对象
	o := orm.NewOrm()
	// 读取用户对象 - 这里使用One方法而不使用Read可以避免读取整个User对象，稍微优化
	// 虽然在这里没有什么卵用，但是如果用户模型比较复杂的情况下还是有效果的
	err = o.QueryTable("user").Filter("username", &user.Username).One(&user, "ID", "password")
	if err != nil {
		l.Debug("用户不存在")
		//c.Ctx.ResponseWriter.WriteHeader(401)
		//c.Data["error"] = "用户不存在或密码错误"
		//flash.Error("用户不存在或密码错误")
		//flash.Store(&c.Controller)
		c.TplName = loginTpl
		return
	}
	// 验证用户密码
	// 第一个参数是数据库存储的加密密码，第二个参数是原密码
	// 密码是单方面加密，不可解密
	// bcrypt包提供的安全加密可以应对大部分的安全要求，具体实现细节请自行百度
	//err = bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(password))
	if user.Password != util.Md5Pwd(password) {
		l.Debug("用户密码错误")
		//c.Ctx.ResponseWriter.WriteHeader(401)
		//c.Data["error"] = "用户不存在或密码错误"
		flash.Error("用户不存在或密码错误")
		flash.Store(&c.Controller)
		c.TplName = loginTpl
		return
	}
	// 设置服务器Session存储
	c.SetSession("uid", user.ID)
	c.SetSession("pwd", password) // 这里存储的是原始密码

	// 返回成功信息
	//c.Data["json"] = util.CreateMsg(200, "用户登陆成功")
	c.Redirect("/", 302)
}

//UserLogoutController 用户登出控制器
type UserLogoutController struct {
	UserStatusController
}

//Get 用户登出响应函数
// @router /logout [get]
func (c *UserLogoutController) Get() {
	// 用户注销
	if !c.isLogin {
		c.Ctx.ResponseWriter.WriteHeader(403)
		// util.CreateMsg - 这个是我自己写的方法，请参考我博文下面的源代码
		//c.Data["json"] = util.CreateMsg(403, "用户未登陆")
		c.ServeJSON()
		return
	}
	// 删除Session - session删除后就不能使用 UserStatusController的 Prepare
	// 来保持登陆状态，即实现注销方法
	c.DestroySession()
	//c.Data["json"] = util.CreateMsg(200, "用户注销成功")
	c.Redirect("/", 302)
}
